﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Drawing;

namespace OpenGL
{
	public enum CameraStagePhase
	{
		None,
		FadeIn,
		Still,
		FadeOut
	}
	public class GLCameraStage
	{
		public override string ToString()
		{
			return "S\t" + Position.X.ToString("0.0") + "\t" + Position.Y.ToString("0.0") + "\t" + Position.Z.ToString("0.0") + "\t" + Target.X.ToString("0.0") + "\t" + Target.Y.ToString("0.0") + "\t" + Target.Z.ToString("0.0");
		}

		public static GLCameraStage FromString(String S)
		{
			return FromString(S.Split('\t'));
		}
		public static GLCameraStage FromString(String[] S)
		{
			return new GLCameraStage(new V3D(double.Parse(S[1]), double.Parse(S[2]), double.Parse(S[3])), new V3D(double.Parse(S[4]), double.Parse(S[5]), double.Parse(S[6])));
		}
		public V3D Position;
		public V3D Target;
		public double FadeTime;

		public CameraStagePhase Phase;
		public virtual void SetPosition(out V3D Pos, out V3D Trg)
		{
			Pos = Position;
			Trg = Target;
		}
		public GLCameraStage(V3D pos, V3D trg)
		{
			Position = pos;
			Target = trg;
			Phase = CameraStagePhase.None;
			FadeTime = 1;
		}

		public void SetCamera()
		{
			GL.gluLook3D(Position, Position + Target, V3D.Green);
		}

		public double fade = 0;

		public void FadeIn(double ft)
		{
			FadeTime = ft;
			Phase = CameraStagePhase.FadeIn;
		}
		public void FadeIn()
		{
			FadeIn(FadeTime);
		}
		public void FadeOut(double ft)
		{
			FadeTime = ft;
			Phase = CameraStagePhase.FadeOut;
		}
		public void FadeOut()
		{
			FadeOut(FadeTime);
		}

		public virtual void Process(double dt)
		{
			switch (Phase)
			{
				case CameraStagePhase.FadeIn:
					fade += dt / FadeTime;
					if (fade >= 1)
					{
						fade = 1;
						Phase = CameraStagePhase.Still;
					}
					break;
				case CameraStagePhase.FadeOut:
					fade -= dt / FadeTime;
					if (fade <= 0)
					{
						fade = 0;
						Phase = CameraStagePhase.None;
					}
					break;
				default:
					break;
			}
		}
	}

	public enum CameraPhase
	{
		Still,
		InitMove,
		Moving,
		EndMove
	}
	public class GLCamera
	{
		protected V3D Position;
		protected V3D ViewDir;

		public List<GLCameraStage> Stages = new List<GLCameraStage>();
		public GLCameraStage currentstage, nextstage;
		public int currentindex = -1;
		int lastindex = -1;
		public double prg = 0;
		double ttrans = 0;

		public int MoveForward(double transfertime)
		{
			if (Phase == CameraPhase.Still)
			{
				if (currentindex < Stages.Count - 1)
				{
					nextstage = Stages[currentindex + 1];
					lastindex = currentindex;
					currentindex++;
					ttrans = transfertime;
					Phase = CameraPhase.InitMove;
					currentstage.FadeOut();
				}
				else
				{
					nextstage = Stages[0];
					lastindex = currentindex;
					currentindex = 0;
					ttrans = transfertime;
					Phase = CameraPhase.InitMove;
				}
			}
			return currentindex;
		}
		public int MoveBackward(double transfertime)
		{
			if (Phase == CameraPhase.Still)
			{
				if (currentindex > 0)
				{
					nextstage = Stages[currentindex - 1];
					lastindex = currentindex;
					currentindex--;
					ttrans = transfertime;
					Phase = CameraPhase.InitMove;
				}
				else
				{
					nextstage = Stages[Stages.Count - 1];
					lastindex = currentindex;
					currentindex = Stages.Count - 1;
					ttrans = transfertime;
					Phase = CameraPhase.InitMove;
				}
			}
			return currentindex;
		}
		public CameraPhase Phase = CameraPhase.Still;
		public GLCamera()
		{
			Stages.Clear();
			currentstage = null;
			currentindex = -1;
			nextstage = null;
		}
		public GLCamera(V3D Pos, V3D Dir)
		{
			Stages.Clear();
			Stages.Add(new GLCameraStage(Pos, Dir));
			currentstage = Stages[0];
			currentindex = 0;
			nextstage = null;
		}
		public void SetView()
		{
			GL.gluLook3D(Position, Position + ViewDir, V3D.Green);
		}
		public virtual void Process(double dt)
		{
			if (currentindex > -1 || currentindex < Stages.Count)
			{
				V3D SP, SN, EN, EP, TS, TE;
				TS = TE = V3D.Black;

				switch (Phase)
				{
					case CameraPhase.Still:
						currentstage.SetPosition(out Position, out ViewDir);
						prg = 0;
						break;
					case CameraPhase.InitMove:
						currentstage.FadeOut(0.3);
						currentstage.Process(dt);
						if (currentstage.Phase == CameraStagePhase.None)
						{
							Phase = CameraPhase.Moving;
						}
						break;
					case CameraPhase.Moving:
						SP = currentstage.Position;
						SN = currentstage.Position;
						EN = nextstage.Position;
						EP = nextstage.Position;
						TS = currentstage.Target;
						TE = nextstage.Target;
						double TargetAngle = V3D.AbsAngleD(TS, TE, V3D.Green);

						prg += dt / ttrans;

						bool end = false;
						if (prg >= 1)
						{
							prg = 1;
							end = true;
						}
						double prg2 = 0.5 - Math.Cos(prg * Math.PI) * 0.5;
						prg2 *= prg2;
						prg2 = prg;

						V3D Trg = TS.CrossPart(V3D.Green).Rot(V3D.Green, V3D.AbsAngleD(TS.CrossPart(V3D.Green), TE.CrossPart(V3D.Green), V3D.Green) * prg2);
						Trg += V3D.Green * Algebra3D.Map(0, 1, prg2, TS.Y, TE.Y);
						
						V3D Pos = Algebra3D.Bezier(SP, SN, EN, EP, prg2);
						Position = Pos;
						ViewDir = Trg;

						if (end)
						{
							currentstage = nextstage;
							nextstage = null;
							Phase = CameraPhase.EndMove;
							prg = 0;
						}
						break;
					case CameraPhase.EndMove:
						currentstage.FadeIn(0.3);
						currentstage.Process(dt);
						if (currentstage.Phase == CameraStagePhase.Still)
						{
							Phase = CameraPhase.Still;
						}
						break;
					default:
						break;
				}
			}
		}

		public void AddStage(GLCameraStage St)
		{
			Stages.Add(St);
			if (Stages.Count == 1)
			{
				currentstage = Stages[0];
				currentindex = 0;
			}
		}
	}
}